﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Services;

using DNNspot.LiveChat.DataModel;
using DotNetNuke.Entities.Users;
using DotNetNuke.Services.Mail;
using EntitySpaces.Interfaces;

namespace DNNspot.LiveChat
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class ChatHandler : IHttpHandler
    {
        // PRIVATE MEMBERS #######################################################

        private HttpContext _context;

        // PROPERTIES ############################################################

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        // EVENTS ################################################################

        public void ProcessRequest(HttpContext context)
        {
            InitializeEntitySpaces();

            _context = context;

            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            response.ContentType = "text/plain";

            //--------------------------------------------
            // DO MESSAGE
            var output = string.Empty;

            switch (request.Params[Params.Action])
            {
                case HandlerActions.CloseSession:
                    output = CloseSession(request.Params);
                    break;
                case HandlerActions.GetOpenSessions:
                    output = GetOpenSessions(request.Params);
                    break;
                case HandlerActions.CreateSession:
                    output = CreateSession(request.Params);
                    break;
                case HandlerActions.EmailSession:
                    output = EmailSession(request.Params);
                    break;
                case HandlerActions.GetSessionState:
                    output = GetSessionState(request.Params);
                    break;
                case HandlerActions.JoinSession:
                    output = JoinSession(request.Params);
                    break;
                case HandlerActions.LoginAgent:
                    output = LoginAgent(request.Params);
                    break;
                case HandlerActions.LogoutAgent:
                    output = LogoutAgent(request.Params);
                    break;
                case HandlerActions.PostMessage:
                    output = PostMessage(request.Params);
                    break;
                case HandlerActions.ReassignSession:
                    output = ReassignSession(request.Params);
                    break;
                case HandlerActions.ReloadQueue:
                    output = ReloadQueue(request.Params);
                    break;
                case HandlerActions.UpdateSession:
                    output = UpdateSession(request.Params);
                    break;
                default:
                    break;
            }

            response.Write(output);
            response.Flush();
        }

        // PRIVATE METHODS #######################################################

        private static string BuildTranscriptString(int sessionId)
        {
            var retVal = string.Empty;

            var q = new MessageQuery();
            q.Where(q.SessionId == sessionId);
            q.OrderBy(q.CreatedDate.Ascending);

            var messages = new MessageCollection();
            messages.Load(q);

            foreach (Message message in messages)
            {
                if (message.AgentId != null || message.ClientId != null)
                {
                    var firstName = message.AgentId != null ? message.UpToAgentByAgentId.UpToUserByUserId.FirstName : message.UpToClientByClientId.FirstName;
                    var lastName = message.AgentId != null ? message.UpToAgentByAgentId.UpToUserByUserId.LastName : message.UpToClientByClientId.LastName;
                    var body = message.Body;

                    lastName = lastName.Substring(0, 1);

                    retVal += String.Format("{0} {1}: {2}<br />", firstName, lastName, body);

                } else
                {
                    retVal += String.Format("{0}<br />", message.Body);
                }
            }

            return retVal;
        }

        private static string CloseSession(NameValueCollection parms)
        {
            var output = string.Empty;

            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var session = new Session();

            if (session.LoadByPrimaryKey(sessionId))
            {
                session.IsActive = false;
                session.Save();
            }

            Message.AddMessage(Convert.ToInt32(session.PortalId), sessionId, null, null, "The chat session has been closed.");

            output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new { Success = !session.IsActive, SessionId = session.SessionId });

            return output;
        }

        private static string CreateSession(NameValueCollection parms)
        {
            var portalId = Convert.ToInt32(parms[Params.PortalId]);
            var userId = Convert.ToInt32(parms[Params.UserId]);
            var clientId = Convert.ToInt32(parms[Params.ClientId]);
            var firstName = parms[Params.FirstName];
            var lastName = parms[Params.LastName];
            var email = parms[Params.Email];
            var ip = parms[Params.Ip];
            var categoryId = Convert.ToInt32(parms[Params.CategoryId]);
            var question = parms[Params.Question];
            var urlReferrer = parms[Params.UrlReferrer];
            var notes = parms[Params.Notes];

            // Load/create client
            var client = new Client();
            client = Client.FindByEmail(email);

            if (client == null) client = Client.Create(portalId, userId, firstName, lastName, email, ip);
            else
            {
                client.Email = email;
                client.FirstName = firstName;
                client.LastName = lastName;
                client.Ip = ip;
                if (userId > 0)
                    client.UserId = userId;
                client.Save();
            }

            // Create new session
            var session = Session.Create(portalId, categoryId, Convert.ToInt32(client.ClientId), urlReferrer, question, notes);

            var welcomeMessage = PortalSetting.Get(portalId, PortalSettingNames.WelcomeMessage) ?? "Waiting for an agent to connect.";

            Message.AddMessage(Convert.ToInt32(session.PortalId), Convert.ToInt32(session.SessionId), null, null, "Session Created.");
            Message.AddMessage(Convert.ToInt32(session.PortalId), Convert.ToInt32(session.SessionId), null, null, welcomeMessage);

            // Serialize data
            var output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new {success = (session != null), SessionId = session.SessionId, ClientId = client.ClientId});

            return output;
        }

        private static string EmailSession(NameValueCollection parms)
        {
            var output = string.Empty;

            var email = Convert.ToString(parms[Params.Email]);
            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var session = new Session();
            session.LoadByPrimaryKey(sessionId);

            var body = String.Format(@"
                <p>
                    <b>Agent Username:</b> {0}<br />
                    <b>Agent Name:</b> {1} {2}<br />
                    <b>Agent Email:</b> {3}
                </p>
                <p>
                    <b>Client Name:</b> {4}{5}<br />
                    <b>Client Email:</b> {6}<br />
                    <b>Client Ip:</b> {7}
                </p>
                <p>
                    <b>Referrer:</b> {8}<br />
                    <b>Category:</b> {9}
                </p>
                <p>                 
                    <b>Question:</b> {10}
                </p>
                <p>                 
                    <b>Notes:</b> {11}
                </p>
                <p>                 
                    <b>Transcript:</b>
                </p>
                <p>
                    {12}
                </p>
                ",
                session.UpToAgentByAgentId.UpToUserByUserId.Username,
                session.UpToAgentByAgentId.UpToUserByUserId.FirstName, 
                session.UpToAgentByAgentId.UpToUserByUserId.LastName,
                session.UpToAgentByAgentId.UpToUserByUserId.Email,
                session.UpToClientByClientId.FirstName,
                session.UpToClientByClientId.LastName,
                session.UpToClientByClientId.Email,
                session.UpToClientByClientId.Ip,
                session.UrlReferrer,
                session.UpToCategoryByCategoryId.GetFullPath(),
                session.Question,
                session.Notes,
                BuildTranscriptString(sessionId));

            var from = DotNetNuke.Entities.Portals.PortalController.GetCurrentPortalSettings().Email;

            var retVal = Mail.SendMail(from, email, "", "Chat transcription", body, "", "HTML", "", "", "", "");
            var success = String.IsNullOrEmpty(retVal);

            retVal = success ? "Success" : retVal;

            output = Newtonsoft.Json.JsonConvert.SerializeObject(new {Success = success, Message = retVal, SessionId = sessionId});

            return output;
        }

        private static string GetOpenSessions(NameValueCollection parms)
        {
            var agentId = Convert.ToInt32(parms[Params.AgentId]);

            var q = new SessionQuery();
            q.Where(q.AgentId == agentId && q.IsActive == true);

            var sessions = new SessionCollection();
            sessions.Load(q);

            var sessionInfos = sessions.ToList().ConvertAll(s => new SessionInfo(s));
            var output = Newtonsoft.Json.JsonConvert.SerializeObject(new { Sessions = sessionInfos });

            return output;
        }

        private static string GetSessionState(NameValueCollection parms)
        {
            var messageId = Convert.ToInt32(parms[Params.MessageId]);
            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var portalId = Convert.ToInt32(parms[Params.PortalId]);
            var success = false;
            
            var timeout = PortalSetting.Get(portalId, PortalSettingNames.QueueTimeout) ?? "180";
            var timeoutMessage = PortalSetting.Get(portalId, PortalSettingNames.QueueTimeoutMessage);

            const int sleepTime = 500;
            const int maxLoadTime = 60*1000;

            // Logged in and waiting for a message
            var messages = new List<Message>();
            var time = 0;

            // Comet implementation
            while (true)
            {
                var session = new Session();
                session.LoadByPrimaryKey(sessionId);

                messages = MessageCollection.GetMessagesAfterMessageId(portalId, sessionId, messageId);
                success = messages.Count > 0 || session.IsActive == false || session.AgentId == null;

                if (session.AgentId == null)
                    if (Convert.ToDateTime(session.CreatedDate).AddSeconds(Convert.ToInt32(timeout)) < DateTime.Now)
                        if (timeoutMessage != null)
                            if (!Message.ExistsByBody(portalId, sessionId, timeoutMessage))
                                Message.AddMessage(portalId, sessionId, null, null, timeoutMessage);

                if (success || time >= maxLoadTime) break;

                System.Threading.Thread.Sleep(sleepTime);
                time += sleepTime;
            }

            // Get back room state: messages and users
            var messageInfos = messages.ConvertAll(m => new MessageInfo(m));

            // Create new RoomState object and conver to JSON
            var sessionState = new SessionState(messageInfos, sessionId, messageId);
            var output = Newtonsoft.Json.JsonConvert.SerializeObject(sessionState);

            return output;
        }

        private static string JoinSession(NameValueCollection parms)
        {
            var output = string.Empty;

            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var agentId = Convert.ToInt32(parms[Params.AgentId]);
            var isJoinedSession = false;
            var session = new Session();

            var agent = new Agent();
            agent.LoadByPrimaryKey(agentId);
            agent.ModifiedDate = DateTime.Now;
            agent.Save();

            if (session.LoadByPrimaryKey(sessionId))
            {
                isJoinedSession = session.AgentId == agentId;
                session.AgentId = agentId;
                session.Save();
            }

            output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new { success = (session != null), IsJoinedSession = isJoinedSession, Session = new SessionInfo(session) });

            return output;
        }

        private static string LoginAgent(NameValueCollection parms)
        {
            var output = string.Empty;

            var agentId = Convert.ToInt32(parms[Params.AgentId]);

            var agent = new Agent();
            agent.LoadByPrimaryKey(agentId);
            agent.IsLoggedIn = true;
            agent.Save();

            return output;
        }

        private static string LogoutAgent(NameValueCollection parms)
        {
            var output = string.Empty;

            var agentId = Convert.ToInt32(parms[Params.AgentId]);

            var agent = new Agent();
            agent.LoadByPrimaryKey(agentId);
            agent.IsLoggedIn = false;
            agent.Save();

            return output;
        }

        private static void InitializeEntitySpaces()
        {
            if (esConfigSettings.ConnectionInfo.Default != "SiteSqlServer")
            {
                esConfigSettings connectionInfoSettings = esConfigSettings.ConnectionInfo;
                foreach (esConnectionElement connection in connectionInfoSettings.Connections)
                {
                    //if there is a SiteSqlServer in es connections set it default
                    if (connection.Name == "SiteSqlServer")
                    {
                        esConfigSettings.ConnectionInfo.Default = connection.Name;
                        return;
                    }
                }

                //no SiteSqlServer found grab dnn cnn string and create
                string dnnConnection = ConfigurationManager.ConnectionStrings["SiteSqlServer"].ConnectionString;

                // Manually register a connection
                esConnectionElement conn = new esConnectionElement();
                conn.ConnectionString = dnnConnection;
                conn.Name = "SiteSqlServer";
                conn.Provider = "EntitySpaces.SqlClientProvider";
                conn.ProviderClass = "DataProvider";
                conn.SqlAccessType = esSqlAccessType.DynamicSQL;
                conn.ProviderMetadataKey = "esDefault";
                conn.DatabaseVersion = "2005";

                // Assign the Default Connection
                esConfigSettings.ConnectionInfo.Connections.Add(conn);
                esConfigSettings.ConnectionInfo.Default = "SiteSqlServer";

                // Register the Loader
                esProviderFactory.Factory = new EntitySpaces.LoaderMT.esDataProviderFactory();
            }
        }

        private string PostMessage(NameValueCollection parms)
        {
            var output = string.Empty;

            int? agentId = parms[Params.AgentId] == null ? (int?) null : Convert.ToInt32(parms[Params.AgentId]);
            int? clientId = parms[Params.ClientId] == null ? (int?) null : Convert.ToInt32(parms[Params.ClientId]);

            int? maxMessagesPerSession = parms[Params.MaxMessagesPerSession] == null ? (int?)null : Convert.ToInt32(parms[Params.MaxMessagesPerSession]);

            var portalId = Convert.ToInt32(parms[Params.PortalId]);
            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var messageBody = parms[Params.MessageBody];

            var session = new Session();
            session.LoadByPrimaryKey(sessionId);

            if (Convert.ToBoolean(session.IsActive))
            {
                messageBody = _context.Server.HtmlEncode(messageBody);

                if (!maxMessagesPerSession.HasValue || (session.MessageCollectionBySessionId.Count < maxMessagesPerSession.Value))
                {
                    //////////////////////////////////////////////
                    // post message
                    Message.AddMessage(portalId, sessionId, agentId, clientId, messageBody);
                }
                else
                {
                    string trialMessage = _context.Server.HtmlEncode("TRIAL MODE - You have reached the message limit for this session. This session will now close.");
                    Message.AddMessage(portalId, sessionId, agentId, clientId, trialMessage);
                    CloseSession(parms);
                }
            }

            return output;
        }

        private string ReassignSession(NameValueCollection parms)
        {
            var output = string.Empty;

            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var session = new Session();

            if (session.LoadByPrimaryKey(sessionId))
            {
                session.AgentId = null;
                session.Save();
            }

            output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new { Success = (session != null), SessionId = sessionId });

            return output;
        }

        private static string ReloadQueue(NameValueCollection parms)
        {
            var agentId = Convert.ToInt32(parms[Params.AgentId]);
            List<int> sessionIds = (parms[Params.SessionIds] ?? "").Split(',').ToList().FindAll(s => !String.IsNullOrEmpty(s)).ConvertAll(s => Convert.ToInt32(s));
            var newSessionIds = sessionIds;
            
            const int sleepTime = 500;
            const int maxLoadTime = 60 * 1000;
            var time = 0;

            var success = false;

            var agent = new Agent();
            agent.LoadByPrimaryKey(agentId);
            agent.ModifiedDate = DateTime.Now;
            agent.Save();

            // Remove inactive agents
            AgentCollection.RemoveAllInactive(90);

            // Comet implementation
            while (true)
            {
                newSessionIds = new List<int>();

                var q = new SessionQuery();
                q.Where(q.AgentId.IsNull() && q.IsActive == true);
                q.OrderBy(q.CreatedDate.Ascending);

                var sessions = new SessionCollection();
                if (sessions.Load(q))
                    sessions.ToList().ForEach(s => newSessionIds.Add(Convert.ToInt32(s.SessionId)));

                success = !sessionIds.SequenceEqual(newSessionIds);

                if (success || time >= maxLoadTime) break;

                System.Threading.Thread.Sleep(sleepTime);
                time += sleepTime;
            }

            var sessionIdOutput = string.Empty;
            foreach (int sessionId in newSessionIds)
            {
                if (sessionIdOutput.Length > 0) 
                    sessionIdOutput += ",";

                sessionIdOutput += sessionId.ToString();
            }

            var output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new { Success = success, 
                    SessionIds = sessionIdOutput,
                      NewSessions = success && (newSessionIds.Count >= sessionIds.Count)
                });

            return output;
        }

        private string UpdateSession(NameValueCollection parms)
        {
            var output = string.Empty;

            var sessionId = Convert.ToInt32(parms[Params.SessionId]);
            var notes = parms[Params.Notes];

            var session = new Session();

            if (session.LoadByPrimaryKey(sessionId))
            {
                session.Notes = notes;
                session.Save();
            }

            output = Newtonsoft.Json.JsonConvert.SerializeObject(
                new { success = (session != null), SessionId = session.SessionId });

            return output;
        }
    }
}
